-
Notifications
You must be signed in to change notification settings - Fork 0
feat: #6 GraphQL Yoga server with searchAlbums #42
Merged
+2,468
−19
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- search/search.ts: websearch_to_tsquery('simple', ...) against the
GIN-indexed search_vector, facet filters that drop their own dimension,
and keyset cursors. Pure and unit-tested.
- loaders/index.ts: per-request DataLoaders for Album.artists and
Album.genres, so a page costs 6 queries whatever its size.
- Drizzle migration for the catalog tables and a 16-album seed.
- docs/VM-setup.md: run steps and the curl smoke suite.
- Root build now builds server/ too, since the deploy job expects
server/dist/index.js once server/ exists.
4 tasks
rachelks
reviewed
Oct 10, 2026
8 tasks
erikhfj
added a commit
that referenced
this pull request
Oct 10, 2026
The workflow built server/ in the runner's own workspace and restarted crate-api, but nothing ever put that build where the unit actually runs it. crate-api.service has WorkingDirectory=~/T19-Project-2/server and ExecStart=.../node dist/index.js, so a restart just brought back whatever had been installed on the machine by hand. Reported by rachelks on #42. The deploy step now: 1. checks out $GITHUB_SHA in ~/T19-Project-2, which is a git clone of main as of this issue rather than a hand copy 2. installs and builds server/ there 3. restarts crate-api and polls until /graphql answers 4. runs a smoke suite through the same Apache proxy the browser uses Three things this fixes that were wrong before: - The unit check is [ -f ... ], not `systemctl list-unit-files | grep`. grep -q closes the pipe at the first match, systemctl dies of SIGPIPE, and with pipefail the pipeline reports failure. The if took the else branch and skipped a service that was definitely there. - The restart needs a NOPASSWD sudoers rule, because the runner has no TTY. Scoped to three systemctl commands. - No sleep. Type=simple reports active before Node binds the port, and a measured 1850 ms startup made `sleep 2` produce a 503 through Apache. It polls instead. Verified on the VM: ~/T19-Project-2 is a real clone at 0fbd54a, git fetch works with the deploy key added for that machine, `checkout --force` leaves .gitignore's .env untouched (it holds DATABASE_URL), and sudo is passwordless for the three commands. The skip guard for `server/` stays, because #42 has not merged yet. Once it does, the next push to main will deploy the API end to end. Co-Authored-By: opencode <noreply@opencode.ai>
erikhfj
added a commit
that referenced
this pull request
Oct 12, 2026
The workflow built server/ in the runner's own workspace and restarted crate-api, but nothing ever put that build where the unit actually runs it. crate-api.service has WorkingDirectory=~/T19-Project-2/server and ExecStart=.../node dist/index.js, so a restart just brought back whatever had been installed on the machine by hand. Reported by rachelks on #42. The deploy step now: 1. checks out $GITHUB_SHA in ~/T19-Project-2, which is a git clone of main as of this issue rather than a hand copy 2. installs and builds server/ there 3. restarts crate-api and polls until /graphql answers 4. runs a smoke suite through the same Apache proxy the browser uses Three things this fixes that were wrong before: - The unit check is [ -f ... ], not `systemctl list-unit-files | grep`. grep -q closes the pipe at the first match, systemctl dies of SIGPIPE, and with pipefail the pipeline reports failure. The if took the else branch and skipped a service that was definitely there. - The restart needs a NOPASSWD sudoers rule, because the runner has no TTY. Scoped to three systemctl commands. - No sleep. Type=simple reports active before Node binds the port, and a measured 1850 ms startup made `sleep 2` produce a 503 through Apache. It polls instead. Verified on the VM: ~/T19-Project-2 is a real clone at 0fbd54a, git fetch works with the deploy key added for that machine, `checkout --force` leaves .gitignore's .env untouched (it holds DATABASE_URL), and sudo is passwordless for the three commands. The skip guard for `server/` stays, because #42 has not merged yet. Once it does, the next push to main will deploy the API end to end. Co-Authored-By: opencode <noreply@opencode.ai>
- Validate cursor values against the sort, so a mismatched cursor gets "Invalid cursor" instead of a Postgres error or wrong page. - Album.reviewCount returns 0 until reviews are stored. - Genre filters combine with AND, as agreed in the group. Genre facet counts now include the selected genres, so "Pop (3)" with Rock ticked matches what ticking Pop returns. Decades stay OR. - Smoke suite expectations and AI usage log updated.
rachelks
approved these changes
Oct 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good now! Both review comments have been addressed, and CI is passing. Approved!
erikhfj
added a commit
that referenced
this pull request
Oct 15, 2026
The workflow built server/ in the runner's own workspace and restarted crate-api, but nothing ever put that build where the unit actually runs it. crate-api.service has WorkingDirectory=~/T19-Project-2/server and ExecStart=.../node dist/index.js, so a restart just brought back whatever had been installed on the machine by hand. Reported by rachelks on #42. The deploy step now: 1. checks out $GITHUB_SHA in ~/T19-Project-2, which is a git clone of main as of this issue rather than a hand copy 2. installs and builds server/ there 3. restarts crate-api and polls until /graphql answers 4. runs a smoke suite through the same Apache proxy the browser uses Three things this fixes that were wrong before: - The unit check is [ -f ... ], not `systemctl list-unit-files | grep`. grep -q closes the pipe at the first match, systemctl dies of SIGPIPE, and with pipefail the pipeline reports failure. The if took the else branch and skipped a service that was definitely there. - The restart needs a NOPASSWD sudoers rule, because the runner has no TTY. Scoped to three systemctl commands. - No sleep. Type=simple reports active before Node binds the port, and a measured 1850 ms startup made `sleep 2` produce a 503 through Apache. It polls instead. Verified on the VM: ~/T19-Project-2 is a real clone at 0fbd54a, git fetch works with the deploy key added for that machine, `checkout --force` leaves .gitignore's .env untouched (it holds DATABASE_URL), and sudo is passwordless for the three commands. The skip guard for `server/` stays, because #42 has not merged yet. Once it does, the next push to main will deploy the API end to end. Co-Authored-By: opencode <noreply@opencode.ai>
erikhfj
added a commit
that referenced
this pull request
Oct 15, 2026
…checks Four points from rachelks on #45. 1. The searchAlbums smoke query omitted `sort`, which is `AlbumSort!` in AlbumSearchInput, so the query failed validation and the check could never pass. Now sends `sort: TITLE_ASC`. 2. The skip guards for crate-api.service and server/ now fail instead of exiting 0. #42 has merged so both are expected, and a deploy job that goes green without deploying anything is the wrong failure mode. 3. The body check parses the JSON rather than grepping for the word "errors". Grep would also fire on an album called "Errors", and it did not verify `data` was present at all. Now rejects a response with errors, with no data, or that is not JSON. Added a totalCount assertion as well, so the check is on the data and not only on its presence. 4. The python validators live in shell variables on one line. A multi-line `python3 -c` heredoc cannot be used inside a YAML block scalar: the block ends at the first unindented line and the workflow stops parsing. Learned by breaking it. Also rebased onto main now that #42 and #43 have merged. Validated the two python expressions against five payloads: a good response, GraphQL errors, null data, a valid totalCount, and two bad ones. Each rejected case prints what was wrong. Co-Authored-By: opencode <noreply@opencode.ai>
erikhfj
added a commit
that referenced
this pull request
Oct 15, 2026
The workflow built server/ in the runner's own workspace and restarted crate-api, but nothing ever put that build where the unit actually runs it. crate-api.service has WorkingDirectory=~/T19-Project-2/server and ExecStart=.../node dist/index.js, so a restart just brought back whatever had been installed on the machine by hand. Reported by rachelks on #42. The deploy step now: 1. checks out $GITHUB_SHA in ~/T19-Project-2, which is a git clone of main as of this issue rather than a hand copy 2. installs and builds server/ there 3. restarts crate-api and polls until /graphql answers 4. runs a smoke suite through the same Apache proxy the browser uses Three things this fixes that were wrong before: - The unit check is [ -f ... ], not `systemctl list-unit-files | grep`. grep -q closes the pipe at the first match, systemctl dies of SIGPIPE, and with pipefail the pipeline reports failure. The if took the else branch and skipped a service that was definitely there. - The restart needs a NOPASSWD sudoers rule, because the runner has no TTY. Scoped to three systemctl commands. - No sleep. Type=simple reports active before Node binds the port, and a measured 1850 ms startup made `sleep 2` produce a 503 through Apache. It polls instead. Verified on the VM: ~/T19-Project-2 is a real clone at 0fbd54a, git fetch works with the deploy key added for that machine, `checkout --force` leaves .gitignore's .env untouched (it holds DATABASE_URL), and sudo is passwordless for the three commands. The skip guard for `server/` stays, because #42 has not merged yet. Once it does, the next push to main will deploy the API end to end. Co-Authored-By: opencode <noreply@opencode.ai>
erikhfj
added a commit
that referenced
this pull request
Oct 15, 2026
…checks Four points from rachelks on #45. 1. The searchAlbums smoke query omitted `sort`, which is `AlbumSort!` in AlbumSearchInput, so the query failed validation and the check could never pass. Now sends `sort: TITLE_ASC`. 2. The skip guards for crate-api.service and server/ now fail instead of exiting 0. #42 has merged so both are expected, and a deploy job that goes green without deploying anything is the wrong failure mode. 3. The body check parses the JSON rather than grepping for the word "errors". Grep would also fire on an album called "Errors", and it did not verify `data` was present at all. Now rejects a response with errors, with no data, or that is not JSON. Added a totalCount assertion as well, so the check is on the data and not only on its presence. 4. The python validators live in shell variables on one line. A multi-line `python3 -c` heredoc cannot be used inside a YAML block scalar: the block ends at the first unindented line and the workflow stops parsing. Learned by breaking it. Also rebased onto main now that #42 and #43 have merged. Validated the two python expressions against five payloads: a good response, GraphQL errors, null data, a valid totalCount, and two bad ones. Each rejected case prints what was wrong. Co-Authored-By: opencode <noreply@opencode.ai>
Sign in
to join this conversation on GitHub.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
What this does
Adds
server/: a GraphQL Yoga API on port 3001 that answerssearchAlbumsfrom the existingschema.graphql, backed by PostgreSQL through Drizzle. This is the core query from the brief: search on single words, case-insensitive, with filtering, sorting and paging done in SQL over the whole result set.Only what the issue asks for is here. No
album(id), no reviews, crate or mutations. Those come with their own issues.How it meets the checkboxes
websearch_to_tsquery('simple', …)against the GIN-indexedsearch_vector.search_vectorholds the album title plus artist names, built withto_tsvector('simple', …).'simple'lowercases and does no stemming, so search is case-insensitive and word-based.EXPLAINconfirms the GIN index is used.genreFacetsignores the selected genres but keeps the search term and decades, anddecadeFacetsdoes the same for decades. Selecting Rock still shows how many Pop or Jazz albums match.search/search.ts, pure and unit-tested. A cursor holds the sort, the last row's sort value and its id. The next page asks for rows strictly after it, so noOFFSET. NULL release years sort last and page correctly. A cursor from a different sort is rejected. 12 Vitest tests.loaders/index.ts.Album.artistsandAlbum.genresload the whole page in one query each. A page costs 6 SQL queries whether it has 3 albums or 50.Files
server/src/index.tsschema.graphql, new loaders per requestserver/src/resolvers.tssearchAlbums(page,totalCount, both facets in parallel) and the Album relation fieldsserver/src/search/search.ts(+.test.ts)server/src/loaders/index.tsserver/src/db/schema.ts,server/drizzle/server/src/db/client.ts,migrate.ts,seed.tsdocs/VM-setup.mdRoot
pnpm buildnow buildsserver/too. The deploy job errors ifserver/exists withoutserver/dist/index.js.Worth knowing
docs/VM-setup.mdis back, but small. docs: #8 record the VM deployment in README and AGENTS.md #41 removed the full runbook. The acceptance line for this issue names that file, so it only holds the smoke suite and five run commands, not a runbook.AVERAGE_RATING_DESCreturns a clear error until reviews are stored.cratewere dropped (backup in~evenkkl/crate-prototype-backup.sql).crate-apistill runs Erik's hand-copied prototype, which now errors on search. Pointing the service at this code is a separate infra task.What was tested
pnpm format:check,lint,typecheck,test,buildEXPLAINof the searchalbum_search_vector_idxCloses
Closes #6.